﻿
package extremefx.drawing {
	import extremefx.ICloneable;
	import extremefx.IDisposable;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.PixelSnapping;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;		

	/**
	 * @author Marcelo Volmaro
	 */
	public final class SlicedBitmap extends Bitmap implements ICloneable, IDisposable {
		private static const _cache:Dictionary = new Dictionary(true);
		private static const _nameCache:Dictionary = new Dictionary();
		private static const _zPoint:Point = new Point(0, 0);
		
		private static var _count:uint = 0;
		
		private var _origBitmap:BitmapData;
		private var _scaleGrid:Rectangle;
		
		private var _repeatX:Boolean;
		private var _repeatY:Boolean;
		private var _name:String;
		private var _thumbH:uint;
		private var _thumbW:uint;
		private var _disposing:Boolean;

		/**
		 * Initializes a new instance of the SlicedBitmap class.
		 * @param pBitmapData
		 */
		public function SlicedBitmap(pBitmapData:BitmapData = null, pRepeatX:Boolean = false, pRepeatY:Boolean = false, pRectangle:Rectangle = null, pThumbW:uint = 0, pThumbH:uint = 0) {
			if (pBitmapData != null) {
				if (!(_name =_nameCache[pBitmapData])) {
					_nameCache[pBitmapData] = _name = "bmp" + (_count++) + "_"+(pRepeatX ? "x" : "-")+"_"+(pRepeatY ? "y" : "-")+"_";
				}
			}
			
			super(pBitmapData, PixelSnapping.ALWAYS, false);
			
			_origBitmap = bitmapData;
			_repeatX = pRepeatX;
			_repeatY = pRepeatY;
			_scaleGrid = pRectangle;
			_thumbW = pThumbW;
			_thumbH = pThumbH;
		}
		
		override public function set width(w:Number):void {
			if (_scaleGrid) w = Math.max(w, _origBitmap.width - _scaleGrid.width);
			if (w == width) return;
			_resizeBitmap(w, height);
		}
		
		override public function set height(h:Number):void {
			if (_scaleGrid) h = Math.max(h, _origBitmap.height - _scaleGrid.height);
			if (h == height) return;
			_resizeBitmap(width, h);
		}
		
		override public function set scale9Grid(r:Rectangle):void {
//			if (r && r.right > _origBitmap.width || r.bottom > _origBitmap.height) {
//				throw (new ArgumentOutOfRangeException("The scale9Grid is bigger than the BitmapData"));
//				return;
//			}
			
			_scaleGrid = r;
			_resizeBitmap(width, height);
		}
		
		override public function get scale9Grid():Rectangle {
			return _scaleGrid;
		}
		
		public function setSize(pW:uint, pH:uint):void {
			if (_scaleGrid){
				pW = Math.max(pW, _origBitmap.width - _scaleGrid.width);
				pH = Math.max(pH, _origBitmap.height - _scaleGrid.height);
			}
			
			_resizeBitmap(pW, pH);
		}
		
		private function _resizeBitmap(pW:uint, pH:uint):void {
			var origBitmap:BitmapData = _origBitmap;
			
			//Do not dispose the original!!!!
			var bmpInfo:_BitmapCacheInfo;
			var cacheItem:String = _name + bitmapData.width + "_" + bitmapData.height; 
			if ((bmpInfo = _cache[cacheItem])){
				if (bitmapData === bmpInfo.bmp){
					bitmapData = bmpInfo.bmp.clone();
					if (!--bmpInfo.count){
						bmpInfo.bmp.dispose();
						bmpInfo.bmp = null;
						delete _cache[cacheItem];
					}
				}  
			}

			//Width && Heights are the same or no scale rectangle? Use the original...
			if (!_scaleGrid || (pW == origBitmap.width && pH == origBitmap.height)){
				bitmapData = origBitmap;
				return;
			}
			
			cacheItem = _name + pW + "_" + pH; 
			if ((bmpInfo = _cache[cacheItem])){
				bitmapData = bmpInfo.bmp;
				bmpInfo.count++;
				return;
			}
			
			var zPoint:Point = _zPoint,
			
			rtop:uint = _scaleGrid.top,
			rleft:uint = _scaleGrid.left,
			rright:uint = _scaleGrid.right,
			rbottom:uint = _scaleGrid.bottom,
			rwidth:uint = _scaleGrid.width,
			rheight:uint = _scaleGrid.height,
			
			thumbSize:uint,
			thumbBmpData:BitmapData,
			middle:Bitmap,
			tempSize:uint;
			
			if (_thumbH){
				thumbSize = (origBitmap.height - _thumbH) >> 1;
				tempSize = origBitmap.width;
				thumbBmpData = new BitmapData(tempSize, (origBitmap.height - _thumbH), true, 0xFFFFFF);
                thumbBmpData.copyPixels(origBitmap, new Rectangle(0, 0, tempSize, thumbSize), zPoint);
                thumbBmpData.copyPixels(origBitmap, new Rectangle(0, (thumbSize + _thumbH), tempSize, (thumbBmpData.height - thumbSize)), new Point(0, thumbSize));
                
                if (pH >= (_thumbH + (pH - rheight))){
                    middle = new Bitmap(new BitmapData(tempSize, _thumbH, true, 0xFFFFFF));
                    middle.bitmapData.copyPixels(origBitmap, new Rectangle(0, thumbSize, tempSize, _thumbH), zPoint);
                };
                
                origBitmap = thumbBmpData;
                
			} else if (_thumbW){
				thumbSize = (origBitmap.width - _thumbW) >> 1;
				tempSize = origBitmap.height;
                thumbBmpData = new BitmapData((origBitmap.width - _thumbW), tempSize, true, 0xFFFFFF);
                thumbBmpData.copyPixels(origBitmap, new Rectangle(0, 0, thumbSize, tempSize), zPoint);
                thumbBmpData.copyPixels(origBitmap, new Rectangle((thumbSize + _thumbW), 0, (thumbBmpData.width - thumbSize), tempSize), new Point(thumbSize, 0));
                if (pW >= (_thumbW + (pW - rwidth))){
                    middle = new Bitmap(new BitmapData(_thumbW, tempSize, true, 0xFFFFFF));
                    middle.bitmapData.copyPixels(bitmapData, new Rectangle(thumbSize, 0, _thumbW, tempSize), zPoint);
                };
                
                origBitmap = thumbBmpData;
			}
			
			var newBitmapData:BitmapData = new BitmapData(pW, pH, true, 0x00000000),
			
			oldR:uint = origBitmap.width - rright, 
			oldB:uint = origBitmap.height - rbottom,
			
			pWoldR:uint = pW - oldR, 
			pHoldB:uint = pH - oldB,
			tPoint:Point = new Point(pWoldR, 0);

			newBitmapData.lock();
			newBitmapData.copyPixels(origBitmap, new Rectangle(0, 0, rleft, rtop), zPoint);//tl
			newBitmapData.copyPixels(origBitmap, new Rectangle(rright, 0, oldR, rtop), tPoint);//tr
			tPoint.y = pHoldB;
			newBitmapData.copyPixels(origBitmap, new Rectangle(rright, rbottom, oldR, oldB), tPoint);//br
			tPoint.x = 0;
			newBitmapData.copyPixels(origBitmap, new Rectangle(0, rbottom, rleft, oldB), tPoint);//bl
			
			var tmp:Sprite = new Sprite();
			var mW:uint = pWoldR - rleft;
			var mH:uint = pHoldB - rtop;
            
            var midbdT:BitmapData = new BitmapData(rwidth, rtop, true, 0xFFFFFF);//top
            midbdT.copyPixels(origBitmap, new Rectangle(rleft, 0, rwidth, rtop), zPoint);
            
            var midbdB:BitmapData = new BitmapData(rwidth, oldB, true, 0xFFFFFF);//bottom
            midbdB.copyPixels(origBitmap, new Rectangle(rleft, rbottom, rwidth, oldB), zPoint);
            
            var midFill:Shape, mid:Bitmap;;
            
            if (_repeatX){
                midFill = new Shape();
                midFill.graphics.beginBitmapFill(midbdT);
                midFill.graphics.drawRect(0, 0, mW, rtop);
                midFill.graphics.beginBitmapFill(midbdB);
                midFill.graphics.drawRect(0, pHoldB, mW, oldB);
                midFill.x = rleft;
                tmp.addChild(midFill);
                
            } else {
            	mid = new Bitmap(midbdT, PixelSnapping.ALWAYS, true);
            	mid.x = rleft;
                mid.width = mW;
                tmp.addChild(mid);
                
                mid = new Bitmap(midbdB, PixelSnapping.ALWAYS, true);
            	mid.x = rleft;
            	mid.y = pHoldB;
                mid.width = mW;
                tmp.addChild(mid);
            }
            
            newBitmapData.draw(tmp);
            
            midbdT.dispose();
            midbdB.dispose();

            var midbdL:BitmapData = new BitmapData(rleft, rheight, true, 0xFFFFFF);//left
            midbdL.copyPixels(origBitmap, new Rectangle(0, rtop, rleft, rheight), zPoint);
            
            var midbdR:BitmapData = new BitmapData(oldR, rheight, true, 0xFFFFFF);//right
            midbdR.copyPixels(origBitmap, new Rectangle(rright, rtop, oldR, rheight), zPoint);
            
            if (_repeatY){
                midFill = new Shape();
                midFill.graphics.beginBitmapFill(midbdL);
                midFill.graphics.drawRect(0, 0, rleft, mH);
                midFill.graphics.beginBitmapFill(midbdR);
                midFill.graphics.drawRect(pWoldR, 0, oldR, mH);
                midFill.y = rtop;
                tmp.addChild(midFill);
                
            } else {
            	mid = new Bitmap(midbdL, PixelSnapping.ALWAYS, true);
            	mid.y = rtop;
                mid.height = mH;
                tmp.addChild(mid);
                
                mid = new Bitmap(midbdR, PixelSnapping.ALWAYS, true);
                mid.x = pWoldR;
            	mid.y = rtop;
            	mid.height = mH;
                tmp.addChild(mid);
            }
            
			newBitmapData.draw(tmp);
			midbdL.dispose();
            midbdR.dispose();
			
			//middle
			var a:Number = mW/rwidth; 
			var d:Number = mH/rheight;
			 
			newBitmapData.draw(
				origBitmap, 
				new Matrix(
					a,
					0,//b
					0,//c
					d,
					rleft * (1 - a),//tx
					rtop * (1 - d) //ty
				),
				null,
				null,
				new Rectangle(rleft, rtop, mW, mH),
				smoothing
			);
			
			if (middle){
				var p:Point;
				if (_thumbH){
					p = new Point(0, (pH - _thumbH) >> 1);
					
				} else {
					p = new Point((pW - _thumbW) >> 1, 0);
				}
				
				newBitmapData.copyPixels(middle.bitmapData, new Rectangle(0, 0, middle.width, middle.height), p);
			}

			newBitmapData.unlock();
			bitmapData = newBitmapData;
			
			if (_cache[cacheItem] == null){
				_cache[cacheItem] = new _BitmapCacheInfo(newBitmapData);
			}
		}

		public function get originalBitmap():BitmapData {
			return _origBitmap;
		}
		
		public function clone():Object {
			var sb:SlicedBitmap = new SlicedBitmap(_origBitmap);
			if (_scaleGrid) sb._scaleGrid = _scaleGrid.clone();
			sb.setSize(width, height);
			return sb; 
		}
		
		public function dispose():void {
			if (_disposing) return;
			_disposing = true;
			
			if (parent) parent.removeChild(this);
			
			var cacheItem:String = _name + bitmapData.width + "_" + bitmapData.height;
			var bmpInfo:_BitmapCacheInfo;
			if ((bmpInfo = _cache[cacheItem])){
				if (bmpInfo.bmp === bitmapData){
					if (--bmpInfo.count){
						return;
					}
					
					bmpInfo.bmp = null;
					delete _cache[cacheItem];
				}
			}
			
			if (bitmapData !== _origBitmap) {
				bitmapData.dispose();
				_origBitmap = null;
			}
			
			_scaleGrid = null;
		}
	}
}

import flash.display.BitmapData;

final class _BitmapCacheInfo {
	public var count:uint;
	public var bmp:BitmapData;

	public function _BitmapCacheInfo(pBmp:BitmapData) {
		count = 1;
		bmp = pBmp;
	}
}